package reports.directors;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;

import coreModel.*;
import reports.builders.IBuilder;

public class Notices implements IDirector, IVisitor {



	
	private HashMap<ProductContainer, Collection<Product>> containerToProductMap; 
	private boolean visited; //this is so i can visit the nodes twice
	private LinkedList<NoticeObj> output;
	
	public Notices()
	{
		visited=false;
		containerToProductMap= new HashMap<ProductContainer, Collection<Product>>();
		output= new LinkedList<NoticeObj>();
	}
	
	
	private void addToOutputList(ProductGroup productGroup, Product p,NoticeObj noticeObj)
	{
		//		Product group su::pg has a 3-month supply (1 pounds) that is 
		//inconsistent with the following products:
		//
		//		    * pg::p (size: 1 count)
		//
		//	
		StorageUnit su = (StorageUnit) HIT.getInstance().getStorageUnitManager().
		getParentStorageUnit(productGroup);
		if(!noticeObj.isHeaderSet())
		{
			
			noticeObj.setHeader("Product group "+su.getName()+"::"+productGroup.getName()+ 
					" has a 3-month supply ("+productGroup.getThreeMonths().toString()+") " +
							"that is inconsistent with the following products:");
		}
		ProductContainer prod = HIT.getInstance().getProductManager().
			locationOfProductInContainer(p,su);
		noticeObj.addElement(""+prod.getName()+"::"+p.getDescription()+
				" (Size:"+p.getSupply().toString()+")");
	}

	@Override
	public void build(IBuilder builder)
	{
		// start the traversal
		HIT.getInstance().getStorageUnitManager().postOrderAccept(this);
		visited=true;
		HIT.getInstance().getStorageUnitManager().preOrderAccept(this);
		
		builder.addTitle("Notices");
		if(output.isEmpty())
		{
			builder.addParagraph("There are no notices as this time");
		}
		else
		{
			builder.addsubTitle("3-Month Supply Warnings");
			for(NoticeObj obj : output)
			{
				builder.addParagraph(obj.getHeader());
				builder.addList(obj.getElements());
			}
		}
		builder.saveToFile();
		
	}


	@Override
	public void visit(ProductGroup productGroup)
	{
		if (visited)
		{
			noticeType  groupType = transform(productGroup.getThreeMonths().getSupplyUnit());
			//get products
			Collection<Product> productList=containerToProductMap.get(productGroup);
			NoticeObj noticeObj = new NoticeObj();
			for(Product p: productList)
			{
				//check it's weight Volume Or Count
				if(transform(p.getUnit())!=groupType)
				{	
					addToOutputList(productGroup,p,noticeObj);
				}
			}
			if(noticeObj.isHeaderSet())
			{
				output.add(noticeObj);
			}
		}
		else
		{
			//get products
			Collection<Product> productList =  new ArrayList<Product>();
				productList.addAll(productGroup.getProducts());
			
			//add all of it's childs products
			for(ProductGroup child: productGroup.getChildren())
			{
				productList.addAll(containerToProductMap.get(child));
			}
			containerToProductMap.put(productGroup, productList);
		}

	}

	@Override
	public void visit(StorageUnit storageUnit)
	{
		
		return;

	}

	@Override
	public void visit(StorageUnitManager suManager)
	{
		
		//do nothing
		return;

	}
	
	public static noticeType transform(Unit unit)
	{

		if (unit == Unit.COUNT)
		{
			return noticeType.COUNT;
		}
		else if (unit == Unit.POUNDS ||
				unit == Unit.OUNCES ||
				unit ==Unit.GRAMS ||
				unit == Unit.KILOGRAMS)
		{
			return noticeType.WEIGHT;
		}
		// GALLONS, QUARTS, PINTS, FLUID_OUNCES, LITERS fall into else
		else
		{
			return noticeType.VOLUME;
		}

	}
	
	

	//add to make things simpler
	private enum noticeType
	{
		WEIGHT,VOLUME,COUNT
	}
	
	
	private class NoticeObj
	{
		private String header;
		private ArrayList<String> elements;	

		public NoticeObj()
		{
			elements = new ArrayList<String>();
		}


		/**
		 * @return the elements
		 */
		public ArrayList<String> getElements()
		{
			return elements;
		}

		/**
		 * @return the header
		 */
		public String getHeader()
		{
			return header;
		}
		public boolean isHeaderSet()
		{
			if(header==null || header.length()==0)
				return false;
			return true;
		}

		/**
		 * @param elements the elements to set
		 */
		public void setElements(ArrayList<String> elements)
		{
			this.elements = elements;
		}
		
		public void addElement(String str)
		{
			elements.add(str);
		}

		/**
		 * @param header the header to set
		 */
		public void setHeader(String header)
		{
			this.header = header;
		}


	}
}
